package com.br.framework.util.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.RandomStringUtils;

import org.jdom2.Document;
import org.jdom2.Element;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;

import com.br.framework.exception.BaseException;
import com.br.framework.model.ThumbnailPolicyModel;
import com.br.framework.model.UploadFileModel;
import com.br.framework.model.UploadPolicyModel;
import com.br.framework.util.lang.DateUtil;
import com.br.framework.util.lang.StringUtil;
import com.br.framework.util.system.PropUtil;
import com.br.framework.util.web.ContextHolder;
import com.br.framework.util.web.ServletUtil;
import com.br.framework.util.web.XmlUtil;

public class FileManager {
	private static final Logger log = LoggerFactory
			.getLogger(FileManager.class);

	/**
	 * 파일네이밍 구분자
	 */
	private static final String NAMING_SEP = PropUtil.getValue("common.upload.naming.separator");
	/**
	 * 시스템 디렉토리 구분자.
	 */
	private static String SYS_SEP = PropUtil.getValue("common.system.separator");

	/**
	 * 업로드 임시 시스템 디렉토리경로
	 */
	private static final String SYS_TEMP_DIR =  PropUtil.getValue("common.upload.system.temp");
	/**
	 * 업로드 임시 웹루트 경로
	 */
	private static final String WEB_TEMP_DIR =  PropUtil.getValue("common.upload.web.temp");
	/**
	 * 업로드 시스템 디렉토리경로
	 */
	private static final String SYS_DIR =  PropUtil.getValue("common.upload.system");
	/**
	 * 업로드 웹로트 경로
	 */
	private static final String WEB_DIR =  PropUtil.getValue("common.upload.web");

	/**
	 * 업로드 정책 XML 파일 경로.
	 */
	private static final String XML_POLICY_PATH =  PropUtil.getValue("common.uploadPolicyXmlPath");

	private String preFix;
	private String suffix;
	private boolean webRootAt;

	/**
	 * 업로드 저장번호.
	 */
	private String uploadNo;
	/**
	 * 업로드 파일 리스트.
	 */
	private List<UploadFileModel> fileList;

	/**
	 * 업로드 정책 모델
	 */
	private UploadPolicyModel uploadPolicy;


	public boolean isWebRootAt() {
		return webRootAt;
	}

	public void setWebRootAt(boolean webRootAt) {
		this.webRootAt = webRootAt;
	}

	public String getUploadNo() {
		return uploadNo;
	}

	public void setUploadNo(String uploadNo) {
		this.uploadNo = uploadNo;
	}


	public List<UploadFileModel> getFileList() {
		return fileList;
	}


	public void setFileList(List<UploadFileModel> fileList) {
		this.fileList = fileList;
	}

	public UploadFileModel getFileModel(){
		if(getFileList() != null && getFileList().size()>0){
			return getFileList().get(0);
		}
		return null;
	}

	public UploadPolicyModel getUploadPolicy() {
		return uploadPolicy;
	}

	public void setUploadPolicy(UploadPolicyModel uploadPolicy) {
		this.uploadPolicy = uploadPolicy;
	}

	/**
	 * 업로드 정책XML파일에서 해당 정책을 읽어 온다.
	 *
	 * @Author Administrator
	 * @param uploadPolicyName
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static UploadPolicyModel getUploadPolicy(String uploadPolicyName) {
		// 기본정책을 담는다.
		UploadPolicyModel defaultModel = new UploadPolicyModel();
		// 전달받은 정책명의 정책을 담는다.
		UploadPolicyModel resultModel = null;
		try {
//			XML 경로를 가지고 온다.
			org.springframework.core.io.Resource resource = ContextHolder.getContext().getResource(XML_POLICY_PATH);
			if (resource == null) {
				throw new BaseException("Exception Code :: UPLOAD POLICY XML");
			}
			File xmlFile = resource.getFile();
			// XML 문서를 가지고 온다.
			Document doc = XmlUtil.getDocument(xmlFile);
			Element root = doc.getRootElement();
			List<Element> itemList = root.getChildren();
			for (Element attrbElement : itemList) {
				String policyName = attrbElement.getAttributeValue("name");
				// 기본정책 체크
				if ("default".equals(policyName)) {
					defaultModel.setPolicyName(policyName);
					defaultModel.setAllowExt(attrbElement.getChildTextTrim("allowExt"));
					defaultModel.setDenyExt(attrbElement.getChildTextTrim("denyExt"));
					defaultModel.setDefaultDir(attrbElement.getChildTextTrim("defaultDir"));
					defaultModel.setLimitSize(attrbElement.getChildTextTrim("limitSize"));
					defaultModel.setWebRootAt(attrbElement.getChildTextTrim("webRootAt"));
					continue;
				}
				if (uploadPolicyName.equals(policyName)) {
					resultModel = new UploadPolicyModel();
					resultModel.setPolicyName(policyName);
					resultModel.setAllowExt(attrbElement
							.getChildTextTrim("allowExt"));
					resultModel.setDenyExt(attrbElement
							.getChildTextTrim("denyExt"));
					// 모든확장자 허용가능일 경우 기본허용불가 확장자를 append
					if ("*".equals(resultModel.getAllowExt())) {
						String denyExt = resultModel.getDenyExt();
						denyExt = "*".equals(denyExt) ? defaultModel.getDenyExt() : denyExt + ","+ defaultModel.getDenyExt();
						resultModel.setDenyExt(denyExt);
					}
					resultModel.setDefaultDir(attrbElement.getChildTextTrim("defaultDir"));
					resultModel.setLimitSize(attrbElement.getChildTextTrim("limitSize"));
					resultModel.setWebRootAt(attrbElement.getChildTextTrim("webRootAt"));
					// 썸네일 정책이 있을 경우 썸네일 정책을 가지고 온다.
					Element thumbnailPolicy = attrbElement.getChild("thumbnail");
					if (thumbnailPolicy != null) {
						List<ThumbnailPolicyModel> thumbModelList = new ArrayList<ThumbnailPolicyModel>();
						List<Element> thumbnailPolicyList = thumbnailPolicy
								.getChildren();
						for (Element attrbThumbnail : thumbnailPolicyList) {
							String attrbName = attrbThumbnail.getName();
							ThumbnailPolicyModel policy = new ThumbnailPolicyModel();
							policy.setName(attrbThumbnail.getAttributeValue("name"));
							if ("crop".equals(attrbName)) {
								String maxWidth = attrbThumbnail.getAttributeValue("maxWidth");
								String xyRatio = attrbThumbnail.getAttributeValue("xyRatio");
								// 썸네일 정책설정
								policy.setCrop(true);
								policy.setMaxWidth(Integer.parseInt(maxWidth));
								policy.setXyRatio(Double.parseDouble(xyRatio));
							} else if ("resize".equals(attrbName)) {
								String width = attrbThumbnail.getAttributeValue("width");
								String height = attrbThumbnail.getAttributeValue("height");
								// 썸네일 정책설정
								policy.setCrop(false);
								policy.setWidth(Integer.parseInt(width));
								policy.setHeight(Integer.parseInt(height));
							}
							thumbModelList.add(policy);
						}
						// 업로드 정책에 썸네일 정책을 추가한다.
						resultModel.setThumbnailPolicyList(thumbModelList);
					}
					break;
				}
			}
		} catch (Exception e) {
			log.error("UPLOAD POLICY XML LOAD FAIL!!!!! >>>>>>>>>> "+ e.getMessage(), e);
		}
		return resultModel == null ? defaultModel : resultModel;
	}

	/**
	 * 파일을 임시폴더에 저장 후 저장번호를 반환한다.
	 * @Author Administrator
	 * @param request
	 * @param policyName
	 * @return
	 * @throws BaseException
	 */
	public  FileManager(HttpServletRequest request,String policyName,String prefix,String suffix,boolean tempAt) throws BaseException{
		if(request instanceof MultipartRequest){
			this.preFix = prefix;
			this.suffix = suffix;
			MultipartRequest multipartRequest = (MultipartRequest)request;
//			업로드 파일맵을 가지고 온다.
			Map<String, MultipartFile>  fileMap = multipartRequest.getFileMap();
//			01. 파일정책을 가지고 온다.
			UploadPolicyModel uploadPolicy = getUploadPolicy(policyName);
			setUploadPolicy(uploadPolicy);
//			02. 파일사이즈를 비교 한다. 허용불가 확장자를 확인한다. 허용가능한 확장자 인지 확인한다.
			checkFile(uploadPolicy,fileMap);
			fileList = new ArrayList<UploadFileModel>();
			for(MultipartFile file:fileMap.values()){
				long fileSize = StringUtil.parseLong(file.getSize());
				String originalFileNm = file.getOriginalFilename();
				String exe = StringUtil.substringAfterLast(originalFileNm, ".");

				UploadFileModel uploadFileModel = new UploadFileModel();
				uploadFileModel.setOriginalFileNm(originalFileNm);
				uploadFileModel.setFileExt(exe);
				uploadFileModel.setFileSize(fileSize);
				uploadFileModel.setContentType(file.getContentType());
				uploadFileModel.setInputName(file.getName());
				String defaultDir = uploadPolicy.getDefaultDir();

//				파일명만들기
//				String uid = UUID.randomUUID().toString();
				String curDate = DateUtil.getToday("yyyyMMddhhmmssS");
				String random = RandomStringUtils.randomAlphabetic(3);
				String uid = curDate+random;
				String uploadFileName = prefix+NAMING_SEP+uid+NAMING_SEP+suffix+"."+exe;
//				해당 업로드가 웹루트 인지 여부
//				웹루트일 경우 컨텍스트 루트를 가지고 온다.
				setWebRootAt(Boolean.parseBoolean(uploadPolicy.getWebRootAt()));
				String sysDir = tempAt?SYS_TEMP_DIR:SYS_DIR;
				String webDir = tempAt?WEB_TEMP_DIR:WEB_DIR;
//				파일명과 파일경로를 setter
				uploadFileModel.setFileName(uploadFileName);
				uploadFileModel.setFilePath(defaultDir+SYS_SEP+uploadFileModel.getFileName());
				String realPath = sysDir+uploadFileModel.getFilePath();
				if(isWebRootAt()){
					uploadFileModel.setFilePath(defaultDir+"/"+uploadFileModel.getFileName());
					realPath = request.getSession().getServletContext().getRealPath(webDir+uploadFileModel.getFilePath());
//					웹루트일 경우 파일경로를 다시 setter
				}
				File srcFile = new File(realPath);
//				디렉토리가 없을 경우 디렉토리 만들기
				if(!srcFile.getParentFile().exists()){
					srcFile.getParentFile().mkdirs();
				}
				try {
					file.transferTo(srcFile);
				} catch (Exception e) {
					throw new BaseException(e,"common.upload.ioException");
				}
				uploadFileModel.setSrcFile(srcFile);
//				썸네일이 있는지. 썸네일은 suffix문자열을 가로사이즈로 변경하여 리네임 한다.
				if(isWebRootAt()
						&& uploadPolicy.getThumbnailPolicyList()!=null
						&& uploadPolicy.getThumbnailPolicyList().size()>0
				){
					for(ThumbnailPolicyModel thumbPolicy : uploadPolicy.getThumbnailPolicyList()){
//						String srcPath = srcFile.getPath();
						if(thumbPolicy.isCrop()){
//							자르기
							int maxWidth = thumbPolicy.getMaxWidth();
							double xyRatio = thumbPolicy.getXyRatio();
							String thumbPath = suffixReplace(srcFile.getPath(),thumbPolicy.getName());
							File thumbFile = new File(thumbPath);
							try {
								ImageUtil.crop(srcFile, thumbFile, maxWidth, xyRatio);
							} catch (IOException e) {
//								썸네일 생성 실패.
								e.printStackTrace();
							}
						}else{
//							축소하기
							int width = thumbPolicy.getWidth();
							int height = thumbPolicy.getHeight();
							String thumbPath = suffixReplace(srcFile.getPath(),thumbPolicy.getName());
							File thumbFile = new File(thumbPath);
							try {
								ImageUtil.resize(srcFile, thumbFile, width, height);
							} catch (IOException e) {
//								썸네일 생성 실패.
								e.printStackTrace();
							}
						}
					}
				}
				fileList.add(uploadFileModel);
			}
//			파입업로드 번호체번
			setUploadNo(UUID.randomUUID().toString());
		}
	}

	/**
	 * 업로드한 파일을 suffx기준으로 replace한다.
	 * @Author Administrator
	 * @param srcPath
	 * @param suffix
	 * @param width
	 * @return
	 */
	public String suffixReplace(String srcPath,String width){
		return  StringUtil.change(srcPath, NAMING_SEP+suffix, NAMING_SEP+width);
	}

	/**
	 * 허용가능한 파일를 확인한다.
	 * @Author Administrator
	 * @param uploadPolicy
	 * @param fileMap
	 * @return
	 * @throws BaseException
	 */
	public void checkFile(UploadPolicyModel uploadPolicy,Map<String, MultipartFile>  fileMap) throws BaseException{
//		업로드 가능 파일 사이즈를 가지고 온다.
		long limitFileSize = StringUtil.parseLong(uploadPolicy.getLimitSize())*1024;
//		허용불가 리스트 가지고 온다.
		String denyExt = uploadPolicy.getDenyExt();
		String denyExts[] = StringUtil.isNotEmpty(denyExt)?StringUtil.split(denyExt, ","):new String[0];
//		허용가능 리스트 가지고 온다.
		String allowExt = uploadPolicy.getAllowExt();
		String allowExts[] = StringUtil.isNotEmpty(allowExt)?StringUtil.split(allowExt, ","):new String[0];
		long totalFileSize = 0;
		for(MultipartFile file:fileMap.values()){
			totalFileSize += file.getSize();
//			허용가능 업로드 체크
			if(totalFileSize>limitFileSize){
				String params[] = {StringUtil.format(limitFileSize/1024)};
				throw new BaseException("common.upload.limitSize",params);
			}
			String originalFileName = file.getOriginalFilename();
			String exe = StringUtil.substringAfterLast(originalFileName, ".");
			if(StringUtil.matchArray(denyExts, exe)){
				String params[] = {exe};
				throw new BaseException("common.upload.denyExt",params);
			}
			if(!"*".equals(allowExt) && !StringUtil.matchArray(allowExts, exe)){
				String params[] = {exe};
				throw new BaseException("common.upload.denyExt",params);
			}
		}
	}

	/**
	 * 임시폴더에 저장된 파일을 목적지로 저장한다.
	 * @Author Administrator
	 */
	public void transferTo(){
		if(fileList!=null
				&& fileList.size()>0
		){
			for(UploadFileModel tmp : fileList){

//				목적지의 디렉토리를 만든다.
				String defaultDir = getUploadPolicy().getDefaultDir();
				String destPath = SYS_DIR+defaultDir;
				if(isWebRootAt()){
					destPath = ServletUtil.getSession().getServletContext().getRealPath(WEB_DIR+defaultDir);
				}
				File destDir = new File(destPath);
				if(!destDir.exists()){
					destDir.mkdirs();
				}

//				복사할 파일목록을 가지고온다.
				File file = tmp.getSrcFile();
				String fileName = file.getName();
				String matchName = StringUtil.change(fileName, NAMING_SEP+suffix, NAMING_SEP);
				File dir = file.getParentFile();
				File files[] = dir.listFiles(new FileMatchFilter(matchName));
				for(File srcFile : files){
					File destFile = new File(destPath+SYS_SEP+srcFile.getName());
					srcFile.renameTo(destFile);
				}
			}
		}
	}

	/**
	 * 임시저장번호로 세션에 저장한다.
	 * @Author Administrator
	 */
	public void saveToSession(){
		ServletUtil.getSession().setAttribute(getUploadNo(), this);
	}

	/**
	 	* @Author Administrator
		 * @param originalFileName : 다운로드시 파일 이름
		 * @param filePath : 다운로드 파일경로
		 * @param webRootAt : 웹루트인지여부
		 * @param request
		 * @param response
		 * @throws BaseException
		 * @throws Exception
	 */
	public static void download(String originalFileName
															, String filePath
															,boolean webRootAt
															, HttpServletResponse response
	) throws BaseException,Exception
	{
		String fileName = URLEncoder.encode(originalFileName, "UTF-8");
		if(webRootAt){
			filePath = ServletUtil.getRequest().getSession().getServletContext().getRealPath(WEB_DIR+filePath);
		}else{
			filePath = SYS_DIR+filePath;
		}
		File file = new File(filePath);

		if (!file.exists())
		{
			throw new BaseException("common.download.ioException");
		}
        InputStream in 		= null;
        OutputStream out 	= null;
        try {
        	in 	= new BufferedInputStream(new FileInputStream(file));
        	out = new BufferedOutputStream(response.getOutputStream());
    		response.setContentType("application/octet-stream");
    		response.setHeader("Content-Transfer-Encoding:", "base64");
    		response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ";");
    		response.setContentLength((int)file.length());
    		byte b[] = new byte[1024];
			int numRead = 0;
			out = response.getOutputStream();
			while ((numRead = in.read(b)) != -1)
			{
				out.write(b, 0, numRead);
			}
			out.flush();
			return;
        } finally {
        	in.close();
   			out.close();
     	}
	}
}
